package com.yanboo.generator.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ZipUtil;
import com.yanboo.generator.config.DataSourceAutoInject;
import com.yanboo.generator.config.DataSourceKeyHolder;
import com.yanboo.generator.config.DynamicRoutingDataSource;
import com.yanboo.generator.constant.DbType;
import com.yanboo.generator.domain.ColumnInfo;
import com.yanboo.generator.domain.GenConfig;
import com.yanboo.generator.domain.vo.TableInfo;
import com.yanboo.generator.service.GeneratorService;
import com.yanboo.generator.service.ProjectInfoService;
import com.yanboo.generator.service.dto.ProjectInfoDto;
import com.yanboo.generator.service.dto.ProjectInfoQueryCriteria;
import jakarta.persistence.EntityManagerFactory;
import lombok.RequiredArgsConstructor;
import com.yanboo.common.utils.FileUtil;
import com.yanboo.common.utils.PageResult;
import com.yanboo.common.utils.PageUtil;
import com.yanboo.common.utils.StringUtils;
import com.yanboo.common.exception.BadRequestException;
import com.yanboo.generator.repository.ColumnInfoRepository;
import com.yanboo.generator.utils.GenUtil;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.internal.SessionImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {
    private static final Logger log = LoggerFactory.getLogger(GeneratorServiceImpl.class);
    @PersistenceContext
    private EntityManager em;

    @Autowired
    private DynamicRoutingDataSource dynamicRoutingDataSource;

    private final ColumnInfoRepository columnInfoRepository;

    private final String CONFIG_MESSAGE = "请先配置生成器";

    @Override
    public Object getTables() {
        List<TableInfo> tableInfos = new ArrayList<>();
        SessionImpl unwrap = em.unwrap(SessionImpl.class);
        JdbcConnectionAccess jdbcConnectionAccess = unwrap.getJdbcConnectionAccess();
        try (Connection connection = jdbcConnectionAccess.obtainConnection();
             ResultSet rs = connection.getMetaData().getTables(connection.getCatalog(), connection.getSchema(), null, null)
        ) {
            while (rs.next()) {
                tableInfos.add(new TableInfo()
                        .setTableName(rs.getString("TABLE_NAME"))
                        .setRemark(rs.getString("REMARKS"))
                );
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return tableInfos;
    }

    @Override
    public PageResult<TableInfo> getTables(String project, String name, int[] startEnd) {
        DataSourceKeyHolder.set(project);
        List<TableInfo> tableInfos = new ArrayList<>();
        SessionImpl unwrap = em.unwrap(SessionImpl.class);
        String[] strings = new String[]{"TABLE"};
        try (Connection connection = unwrap.getJdbcConnectionAccess().obtainConnection();
             ResultSet rs = connection.getMetaData().getTables(connection.getCatalog(), connection.getSchema(), null, strings);
        ) {
            while (rs.next()) {
                tableInfos.add(new TableInfo()
                        .setTableName(rs.getString("TABLE_NAME"))
                        .setRemark(rs.getString("REMARKS"))
                );
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DataSourceKeyHolder.clear();
        }
        List<TableInfo> sortedTableList = tableInfos.stream().sorted(Comparator.comparing(item -> String.valueOf(item.getTableName()))).collect(Collectors.toList());
        if (startEnd[1] > sortedTableList.size()) {
            startEnd[1] = sortedTableList.size();
        }
        List<TableInfo> tableInfoResult = sortedTableList.subList(startEnd[0], startEnd[1]);
        return PageUtil.toPage(tableInfoResult, tableInfos.size());
    }

    @Override
    public List<ColumnInfo> getColumns(String project, String tableName) {
        List<ColumnInfo> columnInfos = columnInfoRepository.findByProjectCodeAndTableNameOrderByIdAsc(project, tableName);
        if (CollectionUtil.isNotEmpty(columnInfos)) {
            return columnInfos;
        } else {
            columnInfos = query(project, tableName);
            return columnInfoRepository.saveAll(columnInfos);
        }
    }

    @Override
    public List<ColumnInfo> query(String project, String tableName) {
        // 使用预编译防止sql注入
        DataSourceKeyHolder.set(project);
        SessionImpl unwrap = em.unwrap(SessionImpl.class);
        List<ColumnInfo> columnInfos = new ArrayList<>();
        List<String> primaryKeyCols = new ArrayList<>();
        List<String> indexCol = new ArrayList<>();
        //  DataSource dataSource = DataSourceAutoInject.dataSourceMap.get(project);
        try (Connection connection = em.unwrap(SessionImpl.class).getJdbcConnectionAccess().obtainConnection();
             ResultSet rs = connection.getMetaData().getColumns(connection.getCatalog(), connection.getSchema(), tableName, null);
             ResultSet primaryKeys = connection.getMetaData().getPrimaryKeys(connection.getCatalog(), connection.getSchema(), tableName);
             ResultSet indexInfo = connection.getMetaData().getIndexInfo(connection.getCatalog(), connection.getSchema(), tableName, true, false)
        ) {
            while (indexInfo.next()) {
                String indexName = indexInfo.getString("INDEX_NAME");
                boolean nonUnique = indexInfo.getBoolean("NON_UNIQUE");
                //非索引
                if (org.apache.commons.lang3.StringUtils.isBlank(indexName) && !nonUnique) {

                }
                indexCol.add(indexInfo.getString("COLUMN_NAME"));
            }
            while (primaryKeys.next()) {
                primaryKeyCols.add(primaryKeys.getString("COLUMN_NAME"));
            }
            while (rs.next()) {
                String colName = rs.getString("COLUMN_NAME");
                String keyType = "";
                if (!indexCol.isEmpty() && indexCol.contains(colName)) {
                    keyType = "UNI";
                }
                if (!primaryKeyCols.isEmpty() && primaryKeyCols.contains(colName)) {
                    keyType = "PRI";
                }
                columnInfos.add(new ColumnInfo()
                        .setTableName(rs.getString("TABLE_NAME"))
                        .setColumnName(colName)
                        .setColumnType(rs.getString("TYPE_NAME"))
                        .setNotNull(rs.getInt("NULLABLE") == 0)
                        .setKeyType(keyType)
                        .setRemark(org.apache.commons.lang3.StringUtils.isBlank(rs.getString("REMARKS")) ? "" : rs.getString("REMARKS"))
                );
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            DataSourceKeyHolder.clear();
        }
        return columnInfos;
    }

    @Override
    public void sync(List<ColumnInfo> columnInfos, List<ColumnInfo> columnInfoList) {
        // 第一种情况，数据库类字段改变或者新增字段
        for (ColumnInfo columnInfo : columnInfoList) {
            // 根据字段名称查找
            List<ColumnInfo> columns = columnInfos.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());
            // 如果能找到，就修改部分可能被字段
            if (CollectionUtil.isNotEmpty(columns)) {
                ColumnInfo column = columns.get(0);
                column.setColumnType(columnInfo.getColumnType());
                column.setExtra(columnInfo.getExtra());
                column.setKeyType(columnInfo.getKeyType());
                if (StringUtils.isBlank(column.getRemark())) {
                    column.setRemark(columnInfo.getRemark());
                }
                columnInfoRepository.save(column);
            } else {
                // 如果找不到，则保存新字段信息
                columnInfoRepository.save(columnInfo);
            }
        }
        // 第二种情况，数据库字段删除了
        for (ColumnInfo columnInfo : columnInfos) {
            // 根据字段名称查找
            List<ColumnInfo> columns = columnInfoList.stream().filter(c -> c.getColumnName().equals(columnInfo.getColumnName())).collect(Collectors.toList());
            // 如果找不到，就代表字段被删除了，则需要删除该字段
            if (CollectionUtil.isEmpty(columns)) {
                columnInfoRepository.delete(columnInfo);
            }
        }
    }

    @Override
    public void save(List<ColumnInfo> columnInfos) {
        columnInfoRepository.saveAll(columnInfos);
    }

    @Override
    public void generator(GenConfig genConfig, List<ColumnInfo> columns) {
        if (genConfig.getId() == null) {
            throw new BadRequestException(CONFIG_MESSAGE);
        }
        try {
            List<ProjectInfoDto> projectInfoDtos = projectInfoService.queryAll(new ProjectInfoQueryCriteria().setCode(genConfig.getProjectCode()));
            DbType dbType;
            if (CollectionUtil.isEmpty(projectInfoDtos)) {
                dbType = DbType.POSTGRES;
            } else {
                dbType =Enum.valueOf( DbType.class, projectInfoDtos.get(0).getDbType());
            }
            GenUtil.generatorCode(columns, genConfig, dbType);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new BadRequestException("生成失败，请手动处理已生成的文件");
        }
    }

    @Autowired
    ProjectInfoService projectInfoService;

    @Override
    public ResponseEntity<Object> preview(GenConfig genConfig, List<ColumnInfo> columns) {
        if (genConfig.getId() == null) {
            throw new BadRequestException(CONFIG_MESSAGE);
        }
        List<ProjectInfoDto> projectInfoDtos = projectInfoService.queryAll(new ProjectInfoQueryCriteria().setCode(genConfig.getProjectCode()));
        DbType dbType;
        if (CollectionUtil.isEmpty(projectInfoDtos)) {
            dbType = DbType.POSTGRES;
        } else {
            dbType = DbType.valueOf(projectInfoDtos.get(0).getDbType());
        }
        List<Map<String, Object>> genList = GenUtil.preview(columns, genConfig, dbType);
        return new ResponseEntity<>(genList, HttpStatus.OK);
    }

    @Override
    public void download(GenConfig genConfig, List<ColumnInfo> columns, HttpServletRequest request, HttpServletResponse response) {
        if (genConfig.getId() == null) {
            throw new BadRequestException(CONFIG_MESSAGE);
        }
        try {
            List<ProjectInfoDto> projectInfoDtos = projectInfoService.queryAll(new ProjectInfoQueryCriteria().setCode(genConfig.getProjectCode()));
            DbType dbType;
            if (CollectionUtil.isEmpty(projectInfoDtos)) {
                dbType = DbType.POSTGRES;
            } else {
                dbType = DbType.valueOf(projectInfoDtos.get(0).getDbType());
            }
            File file = new File(GenUtil.download(columns, genConfig, dbType));
            String zipPath = file.getPath() + ".zip";
            ZipUtil.zip(file.getPath(), zipPath);
            FileUtil.downloadFile(request, response, new File(zipPath), true);
        } catch (IOException e) {
            throw new BadRequestException("打包失败");
        }
    }
}
